package com.chunfeng.mypen.aop;

import com.chunfeng.mypen.auth.UserDetailEntity;
import com.chunfeng.mypen.note.ExcludeMethods;
import com.chunfeng.mypen.note.OtherMethods;
import com.chunfeng.mypen.note.enumType.AopType;
import com.chunfeng.mypen.properties.RedisProperties;
import com.chunfeng.mypen.result.JsonRequest;
import com.chunfeng.mypen.result.RedisDataEntity;
import com.chunfeng.mypen.utils.JsonSerializeUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 全局AOP切面
 * <p>
 * 用于配置全局的缓存机制与事务处理机制
 * <p>
 * <h3>实现原理</h3>
 * 在业务层中,存在事务处理与缓存处理;
 * <p>
 * 一般情况下,这两个逻辑是互斥的,即该方法如果是事务,则一定不是缓存;
 * <p>
 * 该方法如果是缓存,则一定不是事务
 * <p>
 * 但也有特殊,就是用户的自定义逻辑,当该方法本应是缓存但却被识别成了事务,本应是事务却被识别成了缓存
 * 该约定好像就出现了不合理,此时,我们可以
 * 使用{@link ExcludeMethods}进行排除,
 * 使用{@link OtherMethods}进行额外的添加,
 * 具体使用如下:
 * <p>
 * 我们约定:凡是业务层中带有look前缀的方法(配置类可更改),默认执行缓存逻辑,反之执行事务处理
 * <p>
 * 特殊情况下,我们应先使用{@link ExcludeMethods}排除所有,
 * 再使用{@link OtherMethods}添加你想要的逻辑,例如:
 * <p>
 * <p>
 * &#064;{@link Override}<p>
 * &#064;{@link ExcludeMethods}<p>
 * &#064{@link OtherMethods}(type  = {@link AopType})<p>
 * public JsonRequest<String> login(String name, String password) {...<p>}<p>
 * 至此,即可实现对于特殊的事务或缓存处理
 *
 * @author by 春风能解释
 * <p>
 * 2023/11/22
 */
@Aspect
@Component
@EnableAspectJAutoProxy
@Slf4j
public class ServiceAopConfig {
    /**
     * Redis客户端
     */
    @Autowired
    private RedisTemplate<String, ?> redisTemplate;

    /**
     * 事务管理器
     */
    @Autowired
    private DataSourceTransactionManager transactionManager;

    /**
     * Redis配置键
     */
    @Autowired
    private RedisProperties redisProperties;
    /**
     * 类名
     */
    private String objectName;
    /**
     * 方法名
     */
    private String methodName;
    /**
     * 返回值类型(执行前拿到)
     */
    private Class<?> returnType;
    /**
     * 参数
     */
    private String argStr;

    /**
     * 额外切入点
     */
    @Pointcut("@annotation(OtherMethods)")
    private void otherMethods() {
    }

    /**
     * 排除切入点
     * <p>
     * 只要是携带了此注解的，我们认为该方法不应该执行事务和缓存方法，直接全局排除
     */
    @Pointcut("!@annotation(ExcludeMethods)")
    private void excludeMethods() {
    }

    /**
     * 查询切入点
     */
    @Pointcut("execution(* com.chunfeng.mypen.service.impl.*.look*(..))")
    private void selectMethods() {
    }

    /**
     * 业务切入点
     */
    @Pointcut("execution(* com.chunfeng.mypen.service.impl.*.*(..))")
    private void optionService() {
    }

    /**
     * 事务逻辑切入点
     * <p>
     * 我们不检测携带了excludeMethods注解的方法
     */
    @Pointcut("!selectMethods()&&optionService()&&(excludeMethods()&&!otherMethods())")
    private void transaction() {
    }

    /**
     * 缓存切入点
     * <p>
     * 我们不检测携带了excludeMethods注解的方法
     */
    @Pointcut("selectMethods()&&optionService()&&(excludeMethods()&&!otherMethods())")
    private void cache() {
    }

    /**
     * 初始化通知
     *
     * @param pjp 切入点对象
     */
    private void initAOP(JoinPoint pjp) {
        //获取目标对象
        Object pjpTarget = pjp.getTarget();
        //获取方法参数
        List<String> argList = Arrays.stream(pjp.getArgs()).map(Object::toString)
                .collect(Collectors.toList());
        //拿到方法参数
        argStr = String.join(",", argList);
        //反射
        Class<?> aClass = pjpTarget.getClass();
        //拿到切入点的对象名称
        objectName = aClass.getSimpleName();
        //拿到方法名
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        methodName = signature.getName();
        //拿到返回值类型
        returnType = signature.getReturnType();
    }

    /**
     * 业务层控制注解检测
     *
     * @param pjp   切入点对象
     * @param other 扩展注解
     * @return 切入返回值
     */
    @Around("optionService()&&@annotation(other)&!excludeMethods()")
    private Object otherAnnotation(ProceedingJoinPoint pjp, OtherMethods other) {
        Object object = null;
        //添加逻辑
        switch (other.type()) {
            case CACHE_TYPE://加入缓存
                object = redisCache(pjp);
                break;
            case TRANSACTION_TYPE://加入事务
                object = transaction(pjp);
                break;
        }
        return object;
    }

    /**
     * 事务逻辑
     *
     * @param pjp 方法对象
     * @return 方法返回值
     */
    @Around("transaction()")
    private Object transaction(ProceedingJoinPoint pjp) {
        initAOP(pjp);
        //初始化事务处理
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehaviorName("PROPAGATION_REQUIRED");
        TransactionStatus status = transactionManager.getTransaction(definition);
        log.info("{}-{}:事务处理器初始化完成!", objectName, methodName);
        //执行逻辑
        Object proceed;
        try {
            proceed = pjp.proceed();
        } catch (Throwable throwable) {
            log.error("{}-{}:发生异常,事务回滚触发!原因:{}", objectName, methodName, throwable.getLocalizedMessage());
            //回滚事务
            transactionManager.rollback(status);
            return JsonRequest.error(throwable.getLocalizedMessage());
        }
        //获取返回值
        JsonRequest<?> result;
        //判断返回类型
        if (!(proceed instanceof JsonRequest)) {
            // 提交事务
            log.warn("{}-{}:方法返回值不是JSON类型,提交事务触发!", objectName, methodName);
            transactionManager.commit(status);
            return proceed;
        }
        //将返回值转换为实际的
        result = (JsonRequest<?>) proceed;
        //判断JSON数据是否错误
        if (!result.getSuccess()) {
            log.error("{}-{}:执行出错,事务回滚触发!原因:{}", objectName, methodName, result.getMessage());
            transactionManager.rollback(status);
            return proceed;
        }
        log.info("{}-{}:方法执行成功,提交事务触发!", objectName, methodName);
        //删除Redis缓存
        if (redisProperties.getEnable()) {
            //删除缓存
            redisTemplate.delete(objectName);
            log.info("{}-{}:方法执行成功,已成功删除缓存!", objectName, methodName);
        }
        // 提交事务
        transactionManager.commit(status);
        return proceed;
    }


    /**
     * 缓存逻辑
     *
     * @param pjp 方法对象
     * @return 方法返回值
     */
    @Around("cache()")
    private Object redisCache(ProceedingJoinPoint pjp) {
        initAOP(pjp);
        //redis key值
        String key = methodName + "::" + argStr;
        //存在则直接返回
        if (redisProperties.getEnable()) {
            //判断键值是否存在
            if (Boolean.TRUE.equals(redisTemplate.hasKey(objectName))) {
                //获取redis数据
                Object object = redisTemplate.opsForHash().get(objectName, key);
                //存在则直接返回缓存的数据
                if (Objects.nonNull(object)) {
                    log.info("{}-{}:已发现redis缓存!", objectName, methodName);
                    //权限对象的特殊处理
                    if (returnType == UserDetailEntity.class) {
                        return JsonSerializeUtils.reSerialize(object, returnType);
                    }
                    //转换为JSON对象
                    return JsonSerializeUtils
                            .reSerialize(object, RedisDataEntity.class)//Object-->Redis
                            .getJsonRequest();//Redis-->JSON
                }
            }
        }
        //执行逻辑
        Object proceed;
        try {
            proceed = pjp.proceed();
        } catch (Throwable throwable) {
            log.warn("{}-{}:发生异常,异常信息是:{}", objectName, methodName, throwable.getLocalizedMessage());
            return JsonRequest.error(throwable.getLocalizedMessage());
        }
        if (redisProperties.getEnable()) {
            //返回的数据如果不是JSON类型
            if (!(proceed instanceof JsonRequest<?>)) {
                //存
                redisTemplate.opsForHash()
                        .put(objectName, key, proceed);
                return proceed;
            }
            //如果是JSON类型
            else {
                JsonRequest<?> json;
                //强转为JSON类型
                json = (JsonRequest<?>) proceed;
                //只存放成功信息
                if (json.getSuccess()) {
                    //存
                    redisTemplate.opsForHash()
                            .put(objectName, key, new RedisDataEntity<>(json));
                    log.info("{}-{}:已成功缓存!", objectName, methodName);
                }
                //设置过期时间
                redisTemplate.expire(objectName,
                        redisProperties.getTime()
                        , redisProperties.getTimeUnit());
            }
        }
        //返回
        return proceed;
    }
}
